<?php
/**
 * Created by 张彪.
 * User: 小马出行
 * Date: 2018/8/13
 * Time: 9:51
 */

namespace my;

use Elasticsearch\ClientBuilder;
use think\Exception;
use think\Route;

class ElasticSearch
{
    public $client;
    public $max_result_window = 500;//最大返回数
    //
    public $hosts = array('127.0.0.1:9200');

    public function __construct()
    {
        Vendor('Elasticsearch.autoload');
        $this->client = ClientBuilder::create()->setHosts($this->hosts)->build();
    }

    //创建索引
    public function createIndex($index, $mappings = array(), $settings = array('number_of_shards' => 2, 'number_of_replicas' => 1))
    {
        if (empty($index)) {
            return array('status' => 0, 'msg' => "索引名不能为空");
        }
        $indexParams['index'] = $index;
        $indexParams['body']['settings'] = $settings;
        $mappings && $indexParams['body']['mappings'] = $mappings;
        $data = $this->client->indices()->create($indexParams);
        return array('status' => 1, 'data' => $data);
    }

    //删除索引
    public function delIndex($index)
    {
        if (empty($index)) {
            return array('status' => 0, 'msg' => "索引名不能为空");
        }
        $deleteParams = [
            'index' => $index
        ];
        $search = $this->client->indices()->exists($deleteParams);
        if(empty($search)){
            return array('status' => 1, 'data' => '');
        }
        $response = $this->client->indices()->delete($deleteParams);
        return array('status' => 1, 'data' => $response);
    }

    //创建文档
    public function createDoc($index, $type, $id = '', $body)
    {
        if (empty($index) || empty($type)) {
            return array('status' => 0, 'msg' => "缺少参数,无法添加");
        }
        if (!is_array($body) || empty($body)) {
            return array('status' => 0, 'msg' => "数据为空,无法添加");
        }
        $params = [
            'index' => $index,
            'type' => $type,
            'body' => $body
        ];
        $id && $params['id'] = $id;
        $response = $this->client->index($params);
        return array('status' => 1, 'data' => $response);
    }

    //批量创建文档
    public function bulkDoc($index, $type, $body)
    {
        if (empty($index) || empty($type)) {
            return array('status' => 0, 'msg' => "缺少参数,无法添加");
        }
        if (!is_array($body) || empty($body)) {
            return array('status' => 0, 'msg' => "数据为空,无法添加");
        }
        $params = ['body'=> []];
        foreach ($body as $key => $val){
            $params ['body'] [] = [
                'index'=> [
                    '_index'=>$index,
                    '_type'=>$type,
                ]
            ];
            $params ['body'] [] = $val;
            //每1000个文档停止并发送批量请求
            if($key%1000 == 0){
                $responses = $this->client-> bulk($params);
                //删除旧的批量请求
                $params = ['body'=> []];
                //完成节省内存后取消设置批量响应
                unset($responses);
            }
        }
        //发送最后一批(如果存在)
        if(!empty($params ['body'])){
            $responses = $this->client-> bulk($params);
        }
        return array('status' => 1, 'data' => $responses);
    }

    //更新文档
    public function updateDoc($index, $type, $id , $body)
    {
        if (empty($index) || empty($type)|| empty($id)) {
            return array('status' => 0, 'msg' => "缺少参数,无法添加");
        }
        if (!is_array($body) || empty($body)) {
            return array('status' => 0, 'msg' => "数据为空,无法添加");
        }
        $params = [
            'index' => $index,
            'type' => $type,
            'id' => $id,
            'body' => $body
        ];
        $response = $this->client->update($params);
        return array('status' => 1, 'data' => $response);
    }

    //判断文档是否存在
    public function existDoc($params)
    {
        if (array_keys($params) != array('index', 'type', 'id')) {
            return array('status' => 0, 'msg' => "文档条件不能为空");
        }
        $response = $this->client->indices()->exists($params);
        if (empty($response)) {
            return array('status' => 2, 'msg' => "文档不存在");
        }
        return array('status' => 1, 'data' => $response);
    }

    /**
     * 搜索文档
     * @param $index //索引
     * @param $type //类型
     * @param $body //请求体
     * @param $page //页数
     * @param $sort //是否排序
     * @param $sort_param //排序字段
     * @param $column //获取某些字段的值
     * @return  array
     * */
    public function searchDoc($index, $type, $body, $page=1, $sort = false, $sort_param = array('_id'=>'asc'), $column = '')
    {
        if (empty($index) || empty($type) || empty($body)) {
            return array('status' => 0, 'msg' => "文档条件不能为空");
        }
        $size = $this->count($index,$type,$body);
        if(empty($size)){
            return array('status' => 2, 'data' => "没有查到相关数据");
        }
        $p = 1;
        $body['size'] = $this->max_result_window;
        $body['from'] = ($page-1)*$this->max_result_window;
        if($sort){
            $p = ceil($size/$this->max_result_window);
            $body['sort'] = $sort_param;
            $body['from'] = 0;
        }
        if($column){
            if(is_array($column)){
                foreach ($column as $v){
                    $temp[$v] = array();
                }
            }elseif (is_string($column)){
                $temp[$column] = array();
            }
        }
        $params = [
            'index' => $index,
            'type' => $type,
            'body' => $body
        ];
        $data = array();$sort_='';
        for ($i=1;$i<=$p;$i++){
            if($sort){
                if($i==1){
                    $return = $this->dealData($this->client->search($params),$column);
                    $sort_ = $return["sort"];
                }else{
                    $params['body']['search_after'] = $sort_;
                    $return = $this->dealData($this->client->search($params),$column);
                    $sort_ = $return["sort"];
                }
            }else{
                $return = $this->dealData($this->client->search($params),$column);
            }
            if($column){
                if(is_array($column)){
                    foreach ($column as $v){
                        $temp[$v] = array_merge($temp[$v],$return[$v]);
                        unset($return[$v]);
                    }
                }elseif (is_string($column)){
                    $temp[$column] = array_merge($temp[$column],$return[$column]);
                    unset($return[$column]);
                }
            }
            unset($return["sort"]);
            $data = array_merge($data,$return);
            unset($return);
        }
        foreach ($temp as $k => $val){
            $data[$k] = array_unique($val);
        }
        return array('status' => 1, 'data' => $data);
    }

    /**
     * 地理边界搜索
     * @param $index //索引
     * @param $type //类型
     * @param $field //地理边界字段名
     * @param $box //地理边界
     * @param $page //页数
     * @param $sort //是否排序
     * @param $sort_param //排序字段
     * @param $column //获取某些字段的值
     * @return  array
     * */
    public function searchGeoBoxDoc($index, $type, $field, $box, $page=1, $sort = false, $sort_param = array('_id'=>'asc'), $column = '')
    {
        if(empty($field)){
            return array('status' => 0, 'msg' => "搜索字段不能为空");
        }
        if(empty($box)){
            return array('status' => 0, 'msg' => "地理边界参数不能为空");
        }
        $body =
            ["query"=>
                ["bool"=>
                    [/*"must"=>
                        ["match_all"=>new \stdClass()],*/
                    "filter"=>
                        ["geo_bounding_box"=>
                            ["$field" =>
                                [
                                    "top_right"=>$box[0],
                                    "bottom_left"=>$box[1]
                                ]
                            ]
                        ]
                    ]
                ]
                ];
        return $this->searchDoc($index,$type,$body,$page,$sort,$sort_param,$column);
    }

    //删除文档
    public function delDoc($params)
    {
        if (array_keys($params) != array('index', 'type', 'id')) {
            return array('status' => 0, 'msg' => "文档条件不能为空");
        }
        if($this->existDoc($params)['status']==2){
            return array('status' => 1, 'data' => '');
        }
        $response = $this->client->delete($params);
        return array('status' => 1, 'data' => $response);
    }

    /**
     * 处理获得的数据
     * @param $data //搜索后获得的数据
     * @param $field //某些字段单独组成数组数据
     * @param $flag //该字段是否在_source中
     * @return  array
     * */
    private function dealData($data,$field='',$flag = true)
    {
        foreach ($data['hits']['hits'] as $key => $value){
            $value["sort"] && $return['sort'] = $value["sort"];
            $return[] = $value['_source'];
            if($field){
                if(is_array($field)){
                    foreach ($field as $v){
                        $return[$v][] = $flag ? $value['_source'][$v] : $value[$v];
                    }
                }elseif (is_string($field)){
                    $return[$field][] = $flag ? $value['_source'][$field] : $value[$field];
                }
            }
        }
        return $return;
    }

    //统计数据总数
    public function count($index, $type, $body)
    {
        $params = [
            'index' => $index,
            'type' => $type,
            'body' => $body
        ];
        $response = $this->client->count($params);
        return $response['count'];
    }

    /**
     * 获取文档的id
     * @param $index //索引
     * @param $type //属性
     * @param $filter //过滤器
     * @param $flag //是获取id数组，还是id字符串
     * @return  array
     * */
    public function getDocId($index, $type, $filter, $flag = true)
    {
        if (empty($index) || empty($type) || empty($filter)) {
            return array('status' => 0, 'msg' => "文档条件不能为空");
        }
        $body = [
            "query"=>
            ["bool"=>
                ["filter"=>$filter]
            ]
        ];
        $size = $this->count($index,$type,$body);
        if(empty($size)){
            return array('status' => 2, 'data' => "没有查到相关数据");
        }
        $params = [
            "_source"=>false,
            'index' => $index,
            'type' => $type,
            'body' => $body
        ];
        $data = $this->dealData($this->client->search($params),'_id',false)['_id'];
        return array('status' => 1, 'data' => $flag ? $data : $data[0]);
    }

}